package com.owg.jigsaw.slots;

import com.owg.jigsaw.BlockDropAction;
import com.owg.jigsaw.WBlock;
import com.owg.jigsaw.WRoot;
import com.owg.jigsaw.javafx.FXBlock;
import com.owg.jigsaw.javafx.FXUtil;

import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.effect.Blend;
import javafx.scene.effect.BlendMode;
import javafx.scene.effect.ImageInput;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;

public class DropTargetHighlighter
{	
	public class Highlight implements ChangeListener<Transform>
	{	
		private final Node node;
		private final Pane parent;
		private final Rectangle colorizer;
		private final int index;
		private final int xOffset;
		private final int yOffset;
		private Highlight(Node node, Pane parent, Rectangle colorizer, int index, int xOffset, int yOffset)
		{
			super();
			this.parent = parent;
			this.colorizer = colorizer;
			this.node = node;
			this.index = index;
			this.xOffset = xOffset;
			this.yOffset = yOffset;
		}
		public boolean isHorizontal()
		{
			return mySlot.isHorizontal();
		}
		public Slot getSlot()
		{
			return mySlot;
		}
		public int getIndex()
		{
			return index;
		}
		public double getX()
		{
			Point2D p = root.getUI().sceneToLocal(node.localToScene(0, 0));
			return p.getX();
		}
		public double getY()
		{
			Point2D p = root.getUI().sceneToLocal(node.localToScene(0, 0));
			return p.getY();
		}
		public void setHover(boolean b)
		{
			if(b)
				colorizer.setFill(isValid?VALID_HIGHLIGHT:INVALID_HIGHLIGHT);
			else
				colorizer.setFill(isValid?VALID_TARGET:INVALID_TARGET);
		}
		@Override
		public void changed(ObservableValue<? extends Transform> observable,
			Transform oldValue, Transform newValue)
		{
			updatePosition();
		}
		public void updatePosition()
		{
			Point2D p = parent.localToScene(xOffset, yOffset);
			p = root.getUI().sceneToLocal(p);
			node.relocate(p.getX(), p.getY());
		}
	}

	public static Color VALID_TARGET = FXUtil.rgb(0x408040);
	public static Color INVALID_TARGET = FXUtil.rgb(0x800000);
	public static Color VALID_HIGHLIGHT = FXUtil.rgb(0x80FF80);
	public static Color INVALID_HIGHLIGHT = FXUtil.rgb(0xFF0000);

	final WRoot root;
	final Slot mySlot;
	final boolean isValid;
	final Highlight[] highlights;
	
	public DropTargetHighlighter(WRoot root, Slot s, WBlock candidate)
	{
		this.root = root;
		this.mySlot = s;
		int xOffset, yOffset, rotation;
		if(s.isHorizontal())
		{
			xOffset = 0;
			yOffset = FXBlock.TAB_OFFSET;
			rotation = 0;
		}
		else
		{
			xOffset = FXBlock.TAB_OFFSET;
			yOffset = 0;
			rotation = 90;
		}
		
		Pane slotUI = s.getUI();
		
		isValid = s.accepts(candidate) == BlockDropAction.ACCEPT;
		final Color c = isValid?VALID_TARGET:INVALID_TARGET;
		int n = 1;
		if(s.isMultiSlot)
			n += s.getChildren().size();
		highlights = new Highlight[n];
		 
		highlights[0] = mkView(slotUI, 0, s.slotType.get().getContour().getHighlightImage(), c, xOffset, yOffset, rotation);
		
		for(int i = 1; i<n; i++)
		{
			FXBlock ui = (FXBlock) s.getChildren().get(i-1).getUI();
			highlights[i] = mkView(ui.getListConnectorSlot(s.isHorizontal()), i, s.slotType.get().getContour().getHighlightImage(), c, xOffset, yOffset, rotation);
		}
	}
	/**Create a Highlight for one drop target in a slot(multi-slots have multiple Highlights)*/
	private Highlight mkView(Pane parent, int index, ObjectProperty<Image> im, Color c,
		int xOffset, int yOffset, int rotation)
	{
		//Note: Highlights do not update when the slot/tab images change.
		//This can be fixed, but it is difficult to test and not very useful.
		Pane rootUI = root.getUI();
		Node n;
		int t = 1;
		Rectangle colorizer = new Rectangle(im.get().getWidth()+2*t, im.get().getHeight()+2*t);
		colorizer.setFill(c);
		colorizer.setClip(dilate(im.get()));
		colorizer.getTransforms().add(new Rotate(rotation, 0, 0));
		
		n = new Group(colorizer);
		
		rootUI.getChildren().add(1, n);
		//n.toFront();
		//Point2D p = rootUI.sceneToLocal(slotUI.localToScene(xOffset, yOffset));
		//n.relocate(p.getX()-t, p.getY()-t);
		
		/*if(s.isHorizontal())
			xOffset += ui.getRealWidth()-1;
		else
			yOffset += ui.getRealHeight()-1;*/
		
		Highlight h = new Highlight(n, parent, colorizer, index, xOffset, yOffset);
		h.updatePosition();
		parent.localToSceneTransformProperty().addListener(h);
		return h;
	}
	/**Create a node displaying 5 images offset by 0 or 1 pixel along each axis, resulting in dilated alpha.*/
	private Group dilate(Image im)
	{
		Group g = new Group();
		g.setEffect(
			new Blend(BlendMode.SRC_OVER, 
				new Blend(BlendMode.SRC_OVER, 
					new Blend(BlendMode.SRC_OVER, 
						new Blend(BlendMode.SRC_OVER, 
							new ImageInput(im, 1, 1), 
							new ImageInput(im, 1, 0)), 
							new ImageInput(im, 1, 2)), 
							new ImageInput(im, 0, 1)), 
							new ImageInput(im, 2, 1)));
		return g;
	}
	/**Called when the drop target highlighter should remove its UI elements*/
	public void destroy()
	{
		Pane rootUI = root.getUI();
		for(Highlight h : highlights)
		{
			rootUI.getChildren().remove(h.node);
			h.parent.localToSceneTransformProperty().removeListener(h);
		}	
	}
	public Highlight getClosestHighlight(double x, double y)
	{
		if(mySlot.isHorizontal())
		{
			int eastIndex = -1;
			for(int i = 0; i<highlights.length; i++)
			{
				if(highlights[i].getX() > x)
				{
					eastIndex = i;
					break;
				}
			}
			if(eastIndex == -1)
				return highlights[highlights.length-1];
			if(eastIndex == 0)
				return highlights[0];
			if(x < (highlights[eastIndex-1].getX()+highlights[eastIndex].getX())/2)
				return highlights[eastIndex-1];
			return highlights[eastIndex];
		}
		else
		{
			int lowIndex = -1;
			for(int i = 0; i<highlights.length; i++)
			{
				if(highlights[i].getY() > y)
				{
					lowIndex = i;
					break;
				}
			}
			if(lowIndex == -1)
				return highlights[highlights.length-1];
			if(lowIndex == 0)
				return highlights[0];
			if(y < (highlights[lowIndex-1].getY()+highlights[lowIndex].getY())/2)
				return highlights[lowIndex-1];
			return highlights[lowIndex];
		}
	}

	public boolean isHorizontal()
	{
		return mySlot.isHorizontal();
	}
}
